package com.youlu.campus.admin.cert;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.youlu.campus.admin.cert.dto.UserOrderCertDTO;
import com.youlu.campus.entity.*;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import sun.font.FontDesignMetrics;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author : [dengbo]
 * @className : CertMake
 * @description : [描述说明该类的功能]
 * @createTime : [2023/3/26 21:36]
 */
@Service
@Slf4j
public class CertMake {
    public static final String DEFAULT_DIR = "/data/cert-pic";
    public static Map<String, Font> FONT_MAP = new LinkedHashMap<>(10);


    /**
     * 之前放入代码配置文件中的，由于打包太大，后面放在服务器了
     * 读取服务器配置的字体加载
     */
    static {
        try {
            FONT_MAP.put("HeiTi", Font.createFont(Font.TRUETYPE_FONT, ResourceUtil.getFontFileStreamByFileName("ht.otf")));
            FONT_MAP.put("SongTi", Font.createFont(Font.TRUETYPE_FONT, ResourceUtil.getFontFileByFileName("st.otf")));
            FONT_MAP.put("Microsoft YaHei", Font.createFont(Font.TRUETYPE_FONT, ResourceUtil.getFontFileByFileName("wryh.ttf")));
        } catch (Exception e) {
            log.error("init font error", e);
        }
    }

    public static RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5000);
        requestFactory.setReadTimeout(15000);
        return new RestTemplate(requestFactory);
    }

    public byte[] readImageBytes(String url) {
        byte[] imageBytes = getRestTemplate().getForObject(url, byte[].class);
        return imageBytes;
    }


    /**
     * 基于模板制作证书图片
     * @param taskId
     * @param map
     * @return
     */
    public Pair<String, Integer> makeCertPicByTemplete(String taskId, Map<String, List<UserOrderCertDTO>> map) {
        // 创建证书目录
        File path = getCertDir(taskId);
        log.info("cert path:{}", path);
        AtomicInteger totalNum = new AtomicInteger(0);
        map.entrySet().stream().forEach(itemMap -> {
            List<UserOrderCertDTO> data = itemMap.getValue();
            if (CollectionUtils.isEmpty(itemMap.getValue())) {
                return;
            }
            CaTemplate template = data.get(0).getCaTemplate();
            // 生成图片文件
            int num = makeCertFileByTemplate(data, template, path);
            totalNum.addAndGet(num);
        });
        // 生成压缩文件
        String zipFile = makeZipFile(path);
        // 删除文件,保留压缩文件
        deleteFile(path);
        return Pair.of(zipFile, totalNum.get());
    }

    /**
     * 通过模板制作图片
     * @param list
     * @param allCaTemplate
     * @param path
     * @return
     */
    public Integer makeCertFileByTemplate(List<UserOrderCertDTO> list, CaTemplate allCaTemplate, File path) {
        PaperCaTemplateInfo template = allCaTemplate.getPaperCaTemplateInfo();
        if (template == null) {
            log.error("paper cert temp is empty,tempId:{}",allCaTemplate.getId());
            return 0;
        }
        if(StringUtils.isBlank(template.getBackgroundPic().getUrl())){
            log.error("paper cert background pic is empty,tempId:{}",allCaTemplate.getId());
            return 0;
        }
        String certBackPicUrl = template.getBackgroundPic().getUrl();
        byte[] imageBytes = readImageBytes(certBackPicUrl);
        BufferedImage originalImage = null;
        try {
            originalImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
        } catch (IOException e) {
            log.error("template read pic error,Tid:{},Tname:{}", allCaTemplate.getId(), allCaTemplate.getCaName(), e);
            return 0;
        }
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        String ext = StringUtils.isBlank(template.getPicExt()) ? "png" : template.getPicExt();
        int num = 0;
        for (UserOrderCertDTO info : list) {
            try {
                Graphics2D g = newImage.createGraphics();
                g.drawImage(originalImage, 0, 0, null);
                if (template.getCaTemplateUniversityUserName() != null) {

                    CaTemplateUniversityUserName universityUserName = template.getCaTemplateUniversityUserName();
                    log.info(":>>> 证书模版名:{}", JSON.toJSON(template));
                    //根据图片的背景设置水印颜色
                    g.setColor(buildColor(universityUserName.getColor()));
                    Font font = buildFont(universityUserName);
                    g.setFont(font);
                    log.info(":>>> 获取word:{},{},{}",info.getSchoolName(),
                            universityUserName.getSpacesNum(),info.getUserName());
                    String word = addStr(info.getSchoolName()," ", universityUserName.getSpacesNum()) + info.getUserName();
                    XY xy = getXY(universityUserName, font, word);
                    g.drawString(word, xy.x, xy.y);
                }
                if (template.getCertificateNo() != null) {
                    CertificateNoInfo certificateNo = template.getCertificateNo();
                    //根据图片的背景设置水印颜色
                    g.setColor(buildColor(certificateNo.getColor()));
                    Font font = buildFont(certificateNo);
                    g.setFont(font);
                    XY xy = getXY(certificateNo, font, info.getCertNo());
                    g.drawString(info.getCertNo(), xy.x, xy.y);
                }
                if (template.getCaTime() != null) {
                    CaTemplateTime caTime = template.getCaTime();
                    //根据图片的背景设置水印颜色
                    g.setColor(buildColor(caTime.getColor()));
                    Font font = buildFont(caTime);
                    g.setFont(font);
                    XY xy = getXY(template.getCaTime(), font, info.getPublishTime());
                    g.drawString(info.getPublishTime(), xy.x, xy.y);
                }
                g.dispose();
                // 生成证书图片
                ImageIO.write(newImage, ext, getCertPicFile(path, info.getFileName(), ext));
                ++num;
            } catch (Exception e) {
                log.error("userName:{} make cert error", info.getUserName(), e);
            }
        }
        return num;
    }

    /**
     * 重庆护理职业学院,"",2,郑欣悦
     * @param str
     * @param appStr
     * @param numSpaces
     * @return
     */
    public static String addStr(String str,String appStr, int numSpaces) {
        log.info(":>>> 开始拼接证书:{},{},{}",str,appStr,numSpaces);
        StringBuilder builder = new StringBuilder(str);
        for (int i = 0; i < numSpaces; i++) {
            builder.append(appStr);
        }
        log.info(":>>> 结束拼接证书:{}",builder.toString());
        return builder.toString();
    }

    /**
     * 根据模板获取 坐标位置
     * @param base
     * @param font
     * @param text
     * @return
     */
    public XY getXY(CaTemplateBase base, Font font, String text) {
        log.info(":>>> 根据模版获取坐标::{},{},{}",JSON.toJSON(base),font,text);
        FontDesignMetrics fm = FontDesignMetrics.getMetrics(font);
        int textWidth = fm.stringWidth(text);
        int textHeight = fm.getHeight();
        int y = Integer.valueOf(base.getAxisY()) + (Integer.valueOf(base.getHeight()) -textHeight)/2 + fm.getAscent();
        if ("center".equals(base.getAlign())) {
            int x = Integer.valueOf(base.getAxisX()) + Integer.valueOf(base.getWidth()) / 2 - textWidth / 2;
            return new XY(x, y);
        } else if ("left".equals(base.getAlign())) {
            int x = Integer.valueOf(base.getAxisX());
            return new XY(x, y);
        } else {
            int x = Integer.valueOf(base.getAxisX()) + Integer.valueOf(base.getWidth()) - textWidth;
            return new XY(x, y);
        }
    }

    @ToString
    public static class XY {
        private final int x;
        private final int y;

        public XY(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    /**
     * 构建字体
     * @param base
     * @return
     */
    public static Font buildFont(CaTemplateBase base) {
        log.info(":>>> 构建字体:{}",JSON.toJSON(base));
        String fontStr = Font.SERIF.equals(base.getFont())? "HeiTi": base.getFont();
        Font font = FONT_MAP.get(fontStr);
        if (font == null) {
            return new Font(StringUtils.isBlank(base.getFont()) ? Font.SERIF : base.getFont(), base.getFontType(), Integer.valueOf(base.getFontSize()));
        }
        return font.deriveFont(base.getFontType(), Integer.valueOf(base.getFontSize()));
    }

    /**
     * 构建字体颜色
     * @param color
     * @return
     */
    public static Color buildColor(String color) {
        return StringUtils.isBlank(color) ? Color.BLACK : Color.decode(color);
    }

    /**
     * 获取证书图片文件
     * @param path
     * @param fileName
     * @param fileType
     * @return
     */
    public File getCertPicFile(File path, String fileName, String fileType) {
        // generate images with numbered filenames
        File imageFile = new File(path, fileName + "." + fileType);
        log.info("cert real file path:{},filename:{}", imageFile.getAbsolutePath(), fileName);
        return imageFile;
    }

    public static File getCertDir(String taskId) {
        String date =  DateUtil.format(new Date(),"yyyyMMdd");
        // replace with actual number of images
        // create directory with date as name
        String path = DEFAULT_DIR + "/" + date + "/" + taskId;
        log.info("make cert directory :{}", path);
        File directory = new File(path);
        if (!directory.exists()) {
            directory.mkdirs();// 能创建多级目录
        }
        return directory;
    }

    /**
     * 生成证书压缩文件
     * @param directory
     * @return
     */
    public static String makeZipFile(File directory) {
        String zipFile = directory.getPath() + ".zip";
        log.info("zip file name:{}", zipFile);
        // create zip file of directory
        try {
            FileOutputStream fos = new FileOutputStream(zipFile);
            ZipOutputStream zos = new ZipOutputStream(fos);
            zipDirectory(directory, directory.getName(), zos);
            zos.close();
            fos.close();
        } catch (IOException e) {
            log.info("make zip file error",e);
        }
        return zipFile;
    }

    /**
     * 递归压缩文件、目录
     * @param directory
     * @param parent
     * @param zos
     * @throws IOException
     */
    private static void zipDirectory(File directory, String parent, ZipOutputStream zos) throws IOException {
        byte[] buffer = new byte[1024];
        for (File file : directory.listFiles()) {
            if (file.isDirectory()) {
                zipDirectory(file, parent + "/" + file.getName(), zos);
            } else {
                ZipEntry ze = new ZipEntry(parent + "/" + file.getName());
                zos.putNextEntry(ze);
                //对上面封装好的文件构建一个FileInputStream
                FileInputStream fos = new FileInputStream(file);
                int len;
                while ((len = fos.read(buffer)) > 0) {
                    zos.write(buffer, 0, len);
                }
                fos.close();
            }
        }
    }

    /**
     * 删除文件
     * @param file
     */
    public static void deleteFile(File file) {
        if (file == null) {
            return;
        }
        String filename = file.getName();
        if (filename.contains("/")) {
            return;
        }
        if (!file.isDirectory()) {
            boolean re = file.delete();
            log.error("file name:{} delete,re:{}", filename, re);
            return;
        }
        for (File children : file.listFiles()) {
            deleteFile(children);
        }
        boolean re = file.delete();
        log.error("dir name:{} delete,re:{}", filename, re);
    }
}
